Skip to content

Tighten rfc5322 dot placement, fix empty quoted local-part, test length toggle - #63

Merged
mmucklo merged 2 commits into
masterfrom
fix/rfc-strictness-and-options
Jul 22, 2026
Merged

Tighten rfc5322 dot placement, fix empty quoted local-part, test length toggle#63
mmucklo merged 2 commits into
masterfrom
fix/rfc-strictness-and-options

Conversation

@mmucklo

@mmucklo mmucklo commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Three correctness/config improvements to local-part handling, surfaced by differential testing against a reference RFC validator and the dominicsayers/isemail gold-standard corpus.

Changes

rfc5322() dot-atom enforcement (behavior change)

rfc5322() now rejects a leading, trailing, or consecutive dot in the local part (.a@, a.@, a..b@), enforcing dot-atom per RFC 5322 §3.2.3. This matches the actual obs-local-part ABNF (§4.4: word *("." word), words non-empty) — obs-local-part never permitted empty words either.

Escape hatch: the previous permissive dot placement remains available via rfc2822() or ParseOptions::rfc5322()->withAllowObsLocalPart(true). Obs-dot testspec cases moved from normal to relaxed mode to reflect where that behavior now lives.

Empty quoted local-part (""@domain) — bug fix

rejectEmptyQuotedLocalPart (default false) now actually takes effect. Previously ""@domain was rejected as incomplete_address by the state machine before the option was consulted: an empty quote leaves quote_temp empty, and the @ handler used content-emptiness as its "was quoted" signal. The closing-quote handler now records the quote explicitly, and a display-name quote resets the flag so the real local-part stays unquoted ("John Doe" <j@x> → local part j, not "j").

Length limits — documented + tested

The RFC 5321 §4.5.3.1 octet limits can be disabled wholesale via ->withEnforceLengthLimits(false), or customized via ->withLengthLimits(new LengthLimits(...)). This already worked; it's now covered by a test.

Verification

91 → 94 tests (added dot-placement, empty-quoted toggle, length-disable), PHPStan level 8 / Psalm / cs all clean.

Follow-up (not in this PR)

The gold-standard differential surfaced a further ~20 over-acceptance edge cases (unclosed comments / domain-literals, atext-after-quoted-string, CR/LF & folding-whitespace strictness). Those are a separate, larger body of work.

mmucklo added 2 commits July 21, 2026 23:38
…th toggle

Three correctness/config improvements surfaced by differential testing against
a reference validator and the dominicsayers/isemail gold-standard corpus.

- rfc5322() now enforces dot-atom local-part structure (§3.2.3): leading,
  trailing, and consecutive dots are rejected, matching the real obs-local-part
  ABNF (§4.4, non-empty words). The permissive behavior stays available via
  rfc2822() or withAllowObsLocalPart(true). Obs-dot testspec cases moved from
  `normal` to `relaxed` mode accordingly.

- Empty quoted local-part (""@Domain) is now recognized as quoted, so
  rejectEmptyQuotedLocalPart (default false) actually controls its acceptance.
  The closing-quote handler records the quote explicitly; a display-name quote
  resets the flag so the real local-part stays unquoted.

- Documented + tested that enforceLengthLimits(false) disables the 64-octet
  local-part limit (withLengthLimits() for custom limits) — already supported.

91 -> 94 tests, PHPStan level 8 / Psalm / cs all clean.
@mmucklo
mmucklo merged commit ebc9f47 into master Jul 22, 2026
12 checks passed
@mmucklo
mmucklo deleted the fix/rfc-strictness-and-options branch July 22, 2026 06:52
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.87%. Comparing base (41763cf) to head (4db8a9e).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##             master      #63      +/-   ##
============================================
+ Coverage     92.55%   92.87%   +0.32%     
- Complexity      380      381       +1     
============================================
  Files             6        6              
  Lines           981      983       +2     
============================================
+ Hits            908      913       +5     
+ Misses           73       70       -3     
Files with missing lines Coverage Δ
src/Parse.php 90.08% <100.00%> (+0.46%) ⬆️
src/ParseOptions.php 98.93% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant